﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Reflection;
using UnityEngine.Rendering.Universal;
using DG.Tweening;
using ZyGame.Execute;
using ZyGame.Game;

namespace ZyGame.UI
{
    internal class WindowManager : Singleton<WindowManager>
    {
        private Dictionary<Type, UIWindow> viewList = new Dictionary<Type, UIWindow>();
        private Dictionary<Type, UIWindow> cacheList = new Dictionary<Type, UIWindow>();

        internal Camera camera;
        internal Camera topCamera;

        public WindowManager()
        {
            this.camera = new GameObject("UI_Background").AddComponent<Camera>();
            this.camera.cullingMask = 1 << 5;
            this.camera.GetUniversalAdditionalCameraData().volumeLayerMask = this.camera.cullingMask;
            this.camera.GetUniversalAdditionalCameraData().renderType = CameraRenderType.Overlay;

            this.topCamera = new GameObject("UI_Camera").AddComponent<Camera>();
            this.topCamera.cullingMask = this.camera.cullingMask;
            this.topCamera.GetUniversalAdditionalCameraData().renderType = CameraRenderType.Overlay;
            this.topCamera.GetUniversalAdditionalCameraData().volumeLayerMask = this.camera.cullingMask;

            Client.Game.AddGlobalCamera(CameraSort.Background, camera);
            Client.Game.AddGlobalCamera(CameraSort.UICamera, topCamera);
            GameObject.DontDestroyOnLoad(this.camera);
            GameObject.DontDestroyOnLoad(this.topCamera);
            GameObject.DontDestroyOnLoad(GameObject.Find("EventSystem"));
        }

        public UIWindow GetOrOpen(Type viewType, params object[] paramsList)
        {
            UIOptions options = viewType.GetCustomAttribute<UIOptions>();
            if (options == null)
            {
                throw new Exception($"Not find UIOptions Attribute with the {viewType.FullName}");
            }

            if (viewList.TryGetValue(viewType, out UIWindow window))
            {
                Client.Console.WriteLine("already exsit window");
                return window;
            }

            if (cacheList.TryGetValue(viewType, out window))
            {
                Reactive(viewType, options, window, paramsList);
                return window;
            }

            GameObject context = Client.Resource.LoadAsset<GameObject>(options.path);
            if (context is null)
            {
                Client.Console.WriteError("Load Window Prefab Failurefuly:" + options.path);
                return default;
            }

            window = Activator.CreateInstance(viewType) as UIWindow;
            window.gameObject = context;
            Reactive(viewType, options, window, paramsList);
            return window;
        }

        public Runnable<T> GetOrOpenAsync<T>(params object[] paramsList) where T : UIWindow
        {
            Type viewType = typeof(T);
            UIOptions options = viewType.GetCustomAttribute<UIOptions>();
            if (options == null)
            {
                throw new Exception($"Not find UIOptions Attribute with the {viewType.FullName}");
            }

            if (viewList.TryGetValue(viewType, out UIWindow window))
            {
                return Client.Running.FromResult((T)window);
            }

            if (cacheList.TryGetValue(viewType, out window))
            {
                Reactive(viewType, options, window, paramsList);
                return Client.Running.FromResult((T)window);
            }

            if (Client.Running.TryGetOrCreate(viewType.Name, out Runnable<T> runnable))
            {
                return runnable;
            }

            Client.Window.Wait("请稍等...");
            Runnable<GameObject> context = Client.Resource.LoadAssetAsync<GameObject>(options.path);
            if (context is null)
            {
                Client.Console.WriteError("Load Window Prefab Failurefuly:" + options.path);
                return default;
            }

            context.Execute(args =>
            {
                if (args.EnsureExecutedSuccessful() is false)
                {
                    Client.Console.WriteLine($"加载[{options.path}]失败");
                    runnable.SetResult(new NullReferenceException());
                    return;
                }

                window = Activator.CreateInstance(viewType) as UIWindow;
                window.gameObject = context.result;
                Reactive(viewType, options, window, paramsList);
                runnable.SetResult((T)window);
                Client.Window.CloseWait();
            });
            runnable.Execute(context.Waitting);
            return runnable;
        }


        public Runnable<UIWindow> GetOrOpenAsync(Type viewType, params object[] paramsList)
        {
            UIOptions options = viewType.GetCustomAttribute<UIOptions>();
            if (options == null)
            {
                throw new Exception($"Not find UIOptions Attribute with the {viewType.FullName}");
            }

            if (viewList.TryGetValue(viewType, out UIWindow window))
            {
                return Client.Running.FromResult(window);
            }

            if (cacheList.TryGetValue(viewType, out window))
            {
                Reactive(viewType, options, window, paramsList);
                return Client.Running.FromResult(window);
            }

            if (Client.Running.TryGetOrCreate(viewType.Name, out Runnable<UIWindow> runnable))
            {
                return runnable;
            }

            Client.Window.Wait("请稍等...");
            Runnable<GameObject> context = Client.Resource.LoadAssetAsync<GameObject>(options.path);
            if (context is null)
            {
                Client.Console.WriteError("Load Window Prefab Failurefuly:" + options.path);
                return default;
            }

            context.Execute(args =>
            {
                if (args.EnsureExecutedSuccessful() is false)
                {
                    Client.Console.WriteLine($"加载[{options.path}]失败");
                    runnable.SetResult(new NullReferenceException());
                    return;
                }

                window = Activator.CreateInstance(viewType) as UIWindow;
                window.gameObject = context.result;
                Reactive(viewType, options, window, paramsList);
                runnable.SetResult(window);
                Client.Window.CloseWait();
            });
            runnable.Execute(context.Waitting);
            return runnable;
        }

        private void Reactive(Type type, UIOptions options, UIWindow window, params object[] paramsList)
        {
            cacheList.Remove(type);
            window.name = options.name;
            viewList.Add(type, window);
            window.Awake(paramsList);
            window.Enable();
            window.ToLayer(options.layer);
            if (options._opened is not Ease.INTERNAL_Zero)
            {
                GameObject root = window.GetChild("root");
                root.transform.localScale = Vector3.zero;
                root.transform.DOScale(Vector3.one, 0.3f).SetEase(options._opened);
            }
        }

        public UIWindow GetUIView(Type viewType)
        {
            if (viewList.TryGetValue(viewType, out UIWindow view))
            {
                return view;
            }

            return default;
        }

        public void RemoveUIView(Type viewType, bool isCache = false)
        {
            UIWindow view = GetUIView(viewType);
            if (view == null)
            {
                return;
            }

            UIOptions options = viewType.GetCustomAttribute<UIOptions>();
            if (options._closed is Ease.INTERNAL_Zero)
            {
                Completion();
                return;
            }

            GameObject root = view.GetChild("root");
            root.transform.localScale = Vector3.one;
            root.transform.DOScale(Vector3.zero, 0.3f).SetEase(options._closed).OnComplete(Completion);

            void Completion()
            {
                if (isCache)
                {
                    cacheList.Add(viewType, view);
                    view.Disable();
                }
                else
                {
                    view.Dispose();
                }

                viewList.Remove(viewType);
            }
        }

        public void ClearAll()
        {
            foreach (var item in viewList.Values)
            {
                item.Dispose();
            }

            foreach (var item in cacheList.Values)
            {
                item.Dispose();
            }

            cacheList.Clear();
            viewList.Clear();
        }
    }

    class UILayer
    {
        private static Dictionary<int, Canvas> layers = new Dictionary<int, Canvas>();

        public static void To(UIWindow view, int layer)
        {
            if (!layers.TryGetValue(layer, out Canvas canvas))
            {
                canvas = new GameObject("Canvas").AddComponent<Canvas>();
                CanvasScaler scaler = canvas.gameObject.AddComponent<CanvasScaler>();
                scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
                scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.Expand;
                scaler.referenceResolution = AppConfig.instance.gameOptions.screneSize;
                canvas.gameObject.AddComponent<GraphicRaycaster>();
                canvas.sortingOrder = layer;
                canvas.renderMode = RenderMode.ScreenSpaceCamera;
                canvas.worldCamera = layer == 0 ? WindowManager.instance.camera : WindowManager.instance.topCamera;
                canvas.additionalShaderChannels = AdditionalCanvasShaderChannels.None;
                canvas.gameObject.layer = 5;
                layers.Add(layer, canvas);
                GameObject.DontDestroyOnLoad(canvas.gameObject);
            }

            view.gameObject.SetParent(canvas.gameObject, Vector3.zero, Vector3.zero, Vector3.one);
            var rect = view.gameObject.GetComponent<RectTransform>();
            rect.offsetMax = Vector2.zero;
            rect.offsetMin = Vector2.zero;
        }
    }
}